// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/* Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * sc2335 mipi-csi sensor driver code for LomboTech
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 */

#include <linux/init.h>
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/viss_isp.h>

#include <media/lombo_viss.h>

#include <mach/debug.h>
#include "../ccc.h"

/*
 * 2lane 27M 10bit 371.25Mbps 1920X1080 30fps
 */
static const cc_reg_t init_reg_list[] = {
	{ 0x0103, 0x01 },
	{ 0x0100, 0x00 },
	{ 0x36e9, 0x80 },
	{ 0x36f9, 0x80 },
	{ 0x301f, 0x02 },
	{ 0x3207, 0x3f },
	{ 0x3249, 0x0f },
	{ 0x3253, 0x08 },
	{ 0x3271, 0x00 },
	{ 0x3273, 0x03 },
	{ 0x3301, 0x06 },
	{ 0x3302, 0x09 },
	{ 0x3304, 0x28 },
	{ 0x3306, 0x30 },
	{ 0x330b, 0x94 },
	{ 0x330c, 0x08 },
	{ 0x330d, 0x18 },
	{ 0x330e, 0x14 },
	{ 0x330f, 0x05 },
	{ 0x3310, 0x06 },
	{ 0x3314, 0x96 },
	{ 0x3316, 0x00 },
	{ 0x331e, 0x21 },
	{ 0x332b, 0x08 },
	{ 0x3333, 0x10 },
	{ 0x3338, 0x80 },
	{ 0x333a, 0x04 },
	{ 0x334c, 0x04 },
	{ 0x335f, 0x04 },
	{ 0x3364, 0x17 },
	{ 0x3366, 0x62 },
	{ 0x337c, 0x05 },
	{ 0x337d, 0x09 },
	{ 0x337e, 0x00 },
	{ 0x3390, 0x08 },
	{ 0x3391, 0x18 },
	{ 0x3392, 0x38 },
	{ 0x3393, 0x09 },
	{ 0x3394, 0x20 },
	{ 0x3395, 0x20 },
	{ 0x33a2, 0x07 },
	{ 0x33ac, 0x04 },
	{ 0x33ae, 0x14 },
	{ 0x3614, 0x00 },
	{ 0x3622, 0x16 },
	{ 0x3630, 0x68 },
	{ 0x3631, 0x84 },
	{ 0x3637, 0x20 },
	{ 0x363a, 0x1f },
	{ 0x363c, 0x0e },
	{ 0x3670, 0x0e },
	{ 0x3674, 0xa1 },
	{ 0x3675, 0x9c },
	{ 0x3676, 0x9e },
	{ 0x3677, 0x84 },
	{ 0x3678, 0x85 },
	{ 0x3679, 0x87 },
	{ 0x367c, 0x18 },
	{ 0x367d, 0x38 },
	{ 0x367e, 0x08 },
	{ 0x367f, 0x18 },
	{ 0x3690, 0x32 },
	{ 0x3691, 0x32 },
	{ 0x3692, 0x44 },
	{ 0x369c, 0x08 },
	{ 0x369d, 0x38 },
	{ 0x3908, 0x82 },
	{ 0x391f, 0x18 },
	{ 0x3e01, 0x8c },
	{ 0x3e02, 0x00 },
	{ 0x3e03, 0x0b },/* gain mode */
	{ 0x3f00, 0x0d },
	{ 0x3f04, 0x02 },
	{ 0x3f05, 0x0e },
	{ 0x3f09, 0x48 },
	{ 0x4505, 0x0a },
	{ 0x4509, 0x20 },
	{ 0x481d, 0x0a },
	{ 0x4827, 0x03 },
	{ 0x5787, 0x10 },
	{ 0x5788, 0x06 },
	{ 0x578a, 0x10 },
	{ 0x578b, 0x06 },
	{ 0x5790, 0x10 },
	{ 0x5791, 0x10 },
	{ 0x5792, 0x00 },
	{ 0x5793, 0x10 },
	{ 0x5794, 0x10 },
	{ 0x5795, 0x00 },
	{ 0x5799, 0x00 },
	{ 0x57c7, 0x10 },
	{ 0x57c8, 0x06 },
	{ 0x57ca, 0x10 },
	{ 0x57cb, 0x06 },
	{ 0x57d1, 0x10 },
	{ 0x57d4, 0x10 },
	{ 0x57d9, 0x00 },
	{ 0x36e9, 0x20 },
	{ 0x36f9, 0x27 },
	/* { 0x0100, 0x01 }, */
};

static struct cc_power_act power_up_act[] = {
	{CC_RESET, CC_DOWN},
	{CC_PWDN,  CC_DOWN},
	{CC_RESET,  CC_UP},
	{CC_PWDN,  CC_UP},
	{CC_DELAY, 5},
};

static struct cc_power_act power_down_act[] = {
	{CC_PWDN,  CC_DOWN},
	{CC_RESET, CC_DOWN},
};

#define SC2335_1080P_WIDTH		1920
#define SC2335_1080P_HEIGHT		1080

static const struct cc_frame_cfg frame_cfgs[] = {
	{
		.width = SC2335_1080P_WIDTH,
		.height = SC2335_1080P_HEIGHT,
		.framerate = { 1, 30 },	/* 30 fps */
		.mfreq = 371250000,	/* mipi-csi clock */
		.left = 0,
		.top = 0,
		.pclk = 70875000,
		.vts = 1125,
		.hts = 2100,
		.dlanes = 2,
		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
		.colorspace = V4L2_COLORSPACE_JPEG,
		.field = V4L2_FIELD_NONE,
	},
};

static int sensor_detect(struct cc_sensor_info *cc_si)
{
	u8 tmp[2];
	int ret = 0;
	u16 id;

	ret = cc_si->read_reg(cc_si, 0x3107, &tmp[0]);
	if (ret != 0) {
		PRT_ERR("tmp[0]=%x\n", tmp[0]);
		return -ENODEV;
	}
	ret = cc_si->read_reg(cc_si, 0x3108, &tmp[1]);
	if (ret != 0) {
		PRT_ERR("tmp[0]=%x,tmp[1]=%x\n", tmp[0], tmp[1]);
		return -ENODEV;
	}
	id = (tmp[0] << 8) | tmp[1];
	PRT_INFO("id: 0x%04x.\n", id);

	if (id != 0xcb14) {
		PRT_ERR("ID wrong! (0x%04x != 0xcb14)\n", id);
		return -ENODEV;
	}

	return 0;
}

static int sensor_init(struct cc_sensor_info *cc_si)
{
	struct timespec ts_s;
	struct timespec ts_f;
	int ret = 0;

	PRT_INFO("\n");

	ret = sensor_detect(cc_si);
	if (ret != 0)
		return ret;

	ktime_get_ts(&ts_s);
	ret = cc_si->write_reg_list(cc_si, init_reg_list,
					ARRAY_SIZE(init_reg_list));
	if (ret != 0) {
		PRT_ERR("write %s init_reg_list failed!\n", cc_si->name);
		return -ENODEV;
	}
	ktime_get_ts(&ts_f);

	PRT_INFO("write %s init_reg_list finish.\n", cc_si->name);
	PRT_INFO("timeused %d %d\n", (int)(ts_f.tv_sec - ts_s.tv_sec),
		(int)(ts_f.tv_nsec - ts_s.tv_nsec));

	return 0;

}

static int sensor_set_stream(struct cc_sensor_info *cc_si, int on)
{
	int ret = 0;

	if (on) {
		ret = cc_si->write_reg(cc_si, 0x0100, 0x01);
		if (ret)
			PRT_ERR("%d\n", on);
	} else {
		ret = cc_si->write_reg(cc_si, 0x0100, 0x00);
		if (ret)
			PRT_ERR("%d\n", on);
	}
	return ret;
}

/*
 * ctrl for sensor start
 */
static int get_reg(struct cc_sensor_info *cc_si,
			struct viss_isp_reg_data *reg_data)
{
	u8 val = 0;
	int ret = 0;

	/* read */
	ret = cc_si->read_reg(cc_si, reg_data->reg_add, &val);
	reg_data->reg_value = val;

	return ret;
}

static int set_reg(struct cc_sensor_info *cc_si,
			struct viss_isp_reg_data *reg_data)
{
	u8 val = reg_data->reg_value;
	u8 read_val;
	int ret;

	/* write */
	ret = cc_si->write_reg(cc_si, reg_data->reg_add, val);
	PRT_DBG("addr=0x%x,val=0x%02x.\n",
		reg_data->reg_add, reg_data->reg_value);

	ret = cc_si->read_reg(cc_si, reg_data->reg_add, &read_val);
	if (read_val != val)
		PRT_DBG("ret=%d,reg_add=0x%x,val=0x%02x,read_val=0x%02x\n",
			ret, reg_data->reg_add, val, read_val);

	return ret;
}

static int viss_get_sensor_fmt_info(struct cc_sensor_info *cc_si,
		struct viss_isp_sensor_fmt_info *sensor_fmt_info)
{
	int ret = 0;

	sensor_fmt_info->pclk = cc_si->cur_frmcfg->pclk;
	sensor_fmt_info->vts = cc_si->cur_frmcfg->vts;
	sensor_fmt_info->hts = cc_si->cur_frmcfg->hts;
	sensor_fmt_info->hoffset = cc_si->cur_frmcfg->left;
	sensor_fmt_info->voffset = cc_si->cur_frmcfg->top;
	sensor_fmt_info->width = cc_si->cur_frmcfg->width;
	sensor_fmt_info->height = cc_si->cur_frmcfg->height;
	sensor_fmt_info->bayer_mode = ISP_PATTERN_GRBG;
	sensor_fmt_info->buswidth = ISP_INPUT_10BITS;

	return ret;
}

static int viss_isp_set_shutter(struct cc_sensor_info *cc_si,
				__u32 shutter_time)
{
	int ret = 0;

	ret += cc_si->write_reg(cc_si, 0x3e00, (shutter_time >> 12) & 0x0f);
	ret += cc_si->write_reg(cc_si, 0x3e01, (shutter_time >> 4) & 0xff);
	ret += cc_si->write_reg(cc_si, 0x3e02, (shutter_time << 4) & 0xf0);

	return ret;
}

#define SENSOR_MAX_GAIN		(516128) /* 15.875 * 31.75 * 1024 */

static int viss_isp_set_gain(struct cc_sensor_info *cc_si, u32 gain)
{
	int ret = 0;
	u32 again_fine, dgain_fine;
	u32 again = 0, dgain = 0;

	if (gain < 2 * 1024) {
		again = 3;
		again_fine = gain >> 4;
		dgain = 0;
		dgain_fine = (gain * 8 / again_fine);
	} else if (gain <  4 * 1024) {
		again = 7;
		again_fine = gain >> 5;
		dgain = 0;
		dgain_fine = (gain * 4 / again_fine);
	} else if (gain < 8 * 1024) {
		again = 0xf;
		again_fine = gain >> 6;
		dgain = 0;
		dgain_fine = (gain * 2 / again_fine);
	} else if (gain <= 16256) { /* 15.875 * 1024 */
		again = 0x1f;
		again_fine = gain >> 7;
		dgain = 0;
		dgain_fine = (gain / again_fine);
	} else if (gain < 32512) { /* 31.75 * 1024 */
		again = 0x1f;
		again_fine = 0x3f;
		dgain = 0;
		dgain_fine = gain / 127;
	} else if (gain < 65024) { /* 63.5 * 1024 */
		again = 0x1f;
		again_fine = 0x3f;
		dgain = 1;
		dgain_fine = gain / 254;
	} else if (gain < 127 * 1024) {
		again = 0x1f;
		again_fine = 0x3f;
		dgain = 3;
		dgain_fine = gain / 508;
	} else if (gain < 254 * 1024) {
		again = 0x1f;
		again_fine = 0x3f;
		dgain = 7;
		dgain_fine = gain / 1016;
	} else if (gain <= SENSOR_MAX_GAIN) {
		again = 0x1f;
		again_fine = 0x3f;
		dgain = 0xF;
		dgain_fine = gain / 2032;
	} else {
		again = 0x1f;
		again_fine = 0x3f;
		dgain = 0xF;
		dgain_fine = 254;
	}

	ret += cc_si->write_reg(cc_si, 0x3e09, again_fine);
	ret += cc_si->write_reg(cc_si, 0x3e08, again);
	ret += cc_si->write_reg(cc_si, 0x3e07, dgain_fine);
	ret += cc_si->write_reg(cc_si, 0x3e06, dgain);

	return ret;
}

static int __sc2335_set_logic(struct cc_sensor_info *cc_si, u32 gain)
{
	gain >>= 10;
	if (gain < 2)
		cc_si->write_reg(cc_si, 0x363c, 0x0f);
	else
		cc_si->write_reg(cc_si, 0x363c, 0x0f);

	return 0;
}

static int viss_isp_exp_ctrl(struct cc_sensor_info *cc_si,
				struct viss_isp_exp_gain *exp_gain)
{
	int ret = 0;

	ret = viss_isp_set_shutter(cc_si, exp_gain->exp_time / 8);
	if (ret < 0) {
		PRT_ERR("viss_isp_set_shutter failed %d.\n", ret);
		PRT_ERR("%s %u, %u.\n",
			__func__, exp_gain->exp_time, exp_gain->exp_gain);
		return -ENODEV;
	}

	ret = viss_isp_set_gain(cc_si, exp_gain->exp_gain * 4);
	if (ret < 0) {
		PRT_ERR("viss_isp_set_gain failed %d.\n", ret);
		PRT_ERR("%s %u, %u.\n",
			__func__, exp_gain->exp_time, exp_gain->exp_gain);
		return -ENODEV;
	}

	cc_si->write_reg(cc_si, 0x3812, 0x00);

	ret = __sc2335_set_logic(cc_si, exp_gain->exp_gain * 4);
	if (ret)
		PRT_ERR("sc2335 set logic fail.");
	cc_si->write_reg(cc_si, 0x3812, 0x30);

	return ret;
}

/*
 * V4L2 subdev internal operations
 */
static long sensor_ioctl(struct cc_sensor_info *cc_si, unsigned int cmd,
			void *arg)
{
	long ret = 0;

	switch (cmd) {
	case VIDIOC_VISS_GET_SENSOR:
		ret = get_reg(cc_si, (struct viss_isp_reg_data *)arg);
		break;
	case VIDIOC_VISS_SET_SENSOR:
		ret = set_reg(cc_si, (struct viss_isp_reg_data *)arg);
		break;
	case VIDIOC_VISS_ISP_EXP_CTRL:
		ret = viss_isp_exp_ctrl(cc_si,
			(struct viss_isp_exp_gain *)arg);
		break;
	case VIDIOC_VISS_ISP_SENSOR_FMT_INFO:
		ret = viss_get_sensor_fmt_info(cc_si,
			(struct viss_isp_sensor_fmt_info *)arg);
		break;
	default:
		return -EINVAL;
	}
	return ret;
}

/*
 * ctrl for sensor finish
 */


#define DRIVER_NAME "sc2335_mipi"

static int sensor_probe(struct platform_device *pdev)
{
	struct cc_sensor_info *cc_si = NULL;
	int ret = 0;

	PRT_INFO("*********probe begin\n");

	cc_si = devm_kzalloc(&pdev->dev, sizeof(*cc_si), GFP_KERNEL);
	if (cc_si == NULL) {
		PRT_ERR("Can't alloc memory!\n");
		return -ENOMEM;
	}

	cc_si->reg_bits = 16;
	cc_si->val_bits = 8;

	cc_si->name = DRIVER_NAME;

	cc_si->power_up_act = power_up_act;
	cc_si->power_down_act = power_down_act;
	cc_si->npwr_up_acts = ARRAY_SIZE(power_up_act);
	cc_si->npwr_dn_acts = ARRAY_SIZE(power_down_act);

	cc_si->framecfgs = frame_cfgs;
	cc_si->nfrmcfgs = ARRAY_SIZE(frame_cfgs);

	ret = cc_sensor_probe(pdev, cc_si);
	if (ret < 0)
		return ret;

	cc_si->detect = sensor_detect;
	cc_si->sensor_init = sensor_init;
	cc_si->set_stream = sensor_set_stream;
	cc_si->sensor_ioctl = sensor_ioctl;

	PRT_INFO("probe end\n");
	return 0;
}

static int sensor_remove(struct platform_device *pdev)
{
	struct cc_sensor_info *cc_si = platform_get_drvdata(pdev);

	cc_sensor_remove(pdev);

	devm_kfree(&pdev->dev, cc_si);

	return 0;
}

static const struct of_device_id sc2335_mipi_of_match[] = {
	{
		.compatible = DRIVER_NAME,
	},
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, sc2335_mipi_of_match);

static struct platform_driver sc2335_mipi_driver = {
	.driver = {
		.of_match_table	= sc2335_mipi_of_match,
		.name		= DRIVER_NAME,
		.owner		= THIS_MODULE,
	},
	.probe = sensor_probe,
	.remove = sensor_remove,
};

module_platform_driver(sc2335_mipi_driver);

MODULE_DESCRIPTION("SC2335-MIPI sensor driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);

